{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Tce3stUlHN0L"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "cellView": "form",
        "colab": {},
        "colab_type": "code",
        "id": "tuOe1ymfHZPu"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "# 추정기(Estimator)를 사용한 다중 워커(Multi-worker) 훈련\n",
        "\n",
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/distribute/multi_worker_with_estimator\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />TensorFlow.org에서 보기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/tutorials/distribute/multi_worker_with_estimator.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />구글 코랩(Colab)에서 실행하기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/tutorials/distribute/multi_worker_with_estimator.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />깃허브(GitHub) 소스 보기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/tutorials/distribute/multi_worker_with_estimator.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Download notebook</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "hYK9tHrcUfio"
      },
      "source": [
        "Note: 이 문서는 텐서플로 커뮤니티에서 번역했습니다. 커뮤니티 번역 활동의 특성상 정확한 번역과 최신 내용을 반영하기 위해 노력함에도\n",
        "불구하고 [공식 영문 문서](https://www.tensorflow.org/?hl=en)의 내용과 일치하지 않을 수 있습니다.\n",
        "이 번역에 개선할 부분이 있다면\n",
        "[tensorflow/docs-l10n](https://github.com/tensorflow/docs-l10n/) 깃헙 저장소로 풀 리퀘스트를 보내주시기 바랍니다.\n",
        "문서 번역이나 리뷰에 참여하려면\n",
        "[docs-ko@tensorflow.org](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ko)로\n",
        "메일을 보내주시기 바랍니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "xHxb-dlhMIzW"
      },
      "source": [
        "## 개요\n",
        "\n",
        "Note: `tf.distribute` API와 함께 추정기를 사용할 수는 있지만, `tf.distribute`와 함께 케라스(Keras)를 사용하는 것을 추천합니다. [케라스를 사용한 다중 워커(Multi-worker) 훈련](../../guide/multi_worker_with_keras.ipynb)을 봐주세요. `tf.distribute.Strategy`를 추정기와 사용하는 것은 부분적으로만 지원하고 있습니다.\n",
        "\n",
        "이 튜토리얼은 `tf.estimator`와 함께 분산 다중 워커 훈련을 하기 위하여 `tf.distribute.Strategy`를 어떻게 사용하는지 살펴봅니다. `tf.estimator`를 사용하여 코드를 작성하고 있고, 고성능의 장비 한 대로 다룰 수 있는 것보다 더 큰 작업을 하는 데에 관심이 있으시다면 이 튜토리얼이 알맞습니다.\n",
        "\n",
        "시작하기 전에, [텐서플로로 분산 훈련하기](../../guide/distributed_training.ipynb)를 먼저 읽어주세요. [다중 GPU 훈련 튜토리얼](./keras.ipynb)도 관련이 있습니다. 이 튜토리얼과 같은 모델을 사용합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "MUXex9ctTuDB"
      },
      "source": [
        "## 설정\n",
        "\n",
        "먼저, 텐서플로를 설정하고 필요한 패키지들을 가져옵니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "bnYxvfLD-LW-"
      },
      "outputs": [],
      "source": [
        "import tensorflow_datasets as tfds\n",
        "import tensorflow as tf\n",
        "tfds.disable_progress_bar()\n",
        "\n",
        "import os, json"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "hPBuZUNSZmrQ"
      },
      "source": [
        "## 입력 함수\n",
        "\n",
        "이 튜토리얼은 [텐서플로 데이터셋(TensorFlow Datasets)](https://www.tensorflow.org/datasets)의 MNIST 데이터셋을 사용합니다. 코드 내용은 [다중 GPU 훈련 튜토리얼](./keras.ipynb)과 유사하지만 큰 차이점이 하나 있습니다. 바로 추정기를 써서 다중 워커 훈련을 할 때는 데이터셋을 워커 숫자대로 나누어 주어야 모델이 수렴합니다. 입력 데이터는 워커 인덱스로 샤딩(shard)합니다. 그러면 각 워커 프로세스가 데이터셋을 `1/워커 수` 만큼씩 겹치지 않게 나누어 갖습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "dma_wUAxZqo2"
      },
      "outputs": [],
      "source": [
        "BUFFER_SIZE = 10000\n",
        "BATCH_SIZE = 64\n",
        "\n",
        "def input_fn(mode, input_context=None):\n",
        "  datasets, info = tfds.load(name='mnist',\n",
        "                                with_info=True,\n",
        "                                as_supervised=True)\n",
        "  mnist_dataset = (datasets['train'] if mode == tf.estimator.ModeKeys.TRAIN else\n",
        "                   datasets['test'])\n",
        "\n",
        "  def scale(image, label):\n",
        "    image = tf.cast(image, tf.float32)\n",
        "    image /= 255\n",
        "    return image, label\n",
        "\n",
        "  if input_context:\n",
        "    mnist_dataset = mnist_dataset.shard(input_context.num_input_pipelines,\n",
        "                                        input_context.input_pipeline_id)\n",
        "  return mnist_dataset.map(scale).cache().shuffle(BUFFER_SIZE).batch(BATCH_SIZE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "4BlcVXMhB59T"
      },
      "source": [
        "훈련을 수렴시키기 위한 또 다른 방법으로 각 워커에서 데이터셋을 제각기 다른 시드 값으로 셔플하는 것도 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "8YFpxrcsZ2xG"
      },
      "source": [
        "## 다중 워커 설정\n",
        "\n",
        "[다중 GPU 훈련 튜토리얼](./keras.ipynb)과 비교할 때 가장 큰 차이 중 하나는 다중 워커를 설정하는 부분입니다. `TF_CONFIG` 환경 변수는 클러스터를 이루는 각 워커에 클러스터 설정을 지정하는 표준 방법입니다.\n",
        "\n",
        "`TF_CONFIG`에는 `cluster`와 `task`라는 두 가지 구성요소가 있습니다. `cluster`는 전체 클러스터, 다시 말해 클러스터에 속한 워커와 파라미터 서버에 대한 정보를 제공합니다. `task`는 현재 작업에 대한 정보를 지정합니다. 이 예제에서는 작업의 `type`이 `worker`이고, 작업의 `index`는 `0`입니다.\n",
        "\n",
        "예를 들기 위하여, 이 튜토리얼에서는 두 개의 워커를 localhost에 띄울 때의 `TF_CONFIG`를 보여드리겠습니다. 실제로는 각 워커를 다른 장비에서 띄울 텐데, 실제 IP 주소와 포트를 할당하고, 그에 맞게 TF_CONFIG를 지정해야 합니다. 예를 들어, 각 장비의 작업 `index`가 달라야 합니다.\n",
        "\n",
        "주의: 아래 코드를 코랩에서 실행하지 마십시오. 텐서플로 런타임이 주어진 IP와 포트로 gRPC 서버를 띄우려고 할 텐데, 아마도 실패할 것입니다.\n",
        "\n",
        "```\n",
        "os.environ['TF_CONFIG'] = json.dumps({\n",
        "    'cluster': {\n",
        "        'worker': [\"localhost:12345\", \"localhost:23456\"]\n",
        "    },\n",
        "    'task': {'type': 'worker', 'index': 0}\n",
        "})\n",
        "```"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "qDreJzTffAP5"
      },
      "source": [
        "## 모델 정의하기\n",
        "\n",
        "훈련을 위하여 레이어와 옵티마이저, 손실 함수를 정의하세요. 이 튜토리얼에서는 [다중 GPU 훈련 튜토리얼](./keras.ipynb)과 비슷하게 케라스 레이어로 모델을 정의합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "WNvOn_OeiUYC"
      },
      "outputs": [],
      "source": [
        "LEARNING_RATE = 1e-4\n",
        "def model_fn(features, labels, mode):\n",
        "  model = tf.keras.Sequential([\n",
        "      tf.keras.layers.Conv2D(32, 3, activation='relu', input_shape=(28, 28, 1)),\n",
        "      tf.keras.layers.MaxPooling2D(),\n",
        "      tf.keras.layers.Flatten(),\n",
        "      tf.keras.layers.Dense(64, activation='relu'),\n",
        "      tf.keras.layers.Dense(10)\n",
        "  ])\n",
        "  logits = model(features, training=False)\n",
        "\n",
        "  if mode == tf.estimator.ModeKeys.PREDICT:\n",
        "    predictions = {'logits': logits}\n",
        "    return tf.estimator.EstimatorSpec(labels=labels, predictions=predictions)\n",
        "\n",
        "  optimizer = tf.compat.v1.train.GradientDescentOptimizer(\n",
        "      learning_rate=LEARNING_RATE)\n",
        "  loss = tf.keras.losses.SparseCategoricalCrossentropy(\n",
        "      from_logits=True, reduction=tf.keras.losses.Reduction.NONE)(labels, logits)\n",
        "  loss = tf.reduce_sum(loss) * (1. / BATCH_SIZE)\n",
        "  if mode == tf.estimator.ModeKeys.EVAL:\n",
        "    return tf.estimator.EstimatorSpec(mode, loss=loss)\n",
        "\n",
        "  return tf.estimator.EstimatorSpec(\n",
        "      mode=mode,\n",
        "      loss=loss,\n",
        "      train_op=optimizer.minimize(\n",
        "          loss, tf.compat.v1.train.get_or_create_global_step()))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "P94PrIW_kSCE"
      },
      "source": [
        "Note: 이 예제에서는 학습률이 고정되어있습니다. 하지만 실제로는 전역 배치 크기에 따라 학습률을 조정해야 할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "UhNtHfuxCGVy"
      },
      "source": [
        "## MultiWorkerMirroredStrategy\n",
        "\n",
        "모델을 훈련하기 위하여 `tf.distribute.experimental.MultiWorkerMirroredStrategy`의 인스턴스를 사용하세요. `MultiWorkerMirroredStrategy`는 모든 워커의 각 장비에, 모델의 레이어에 있는 모든 변수의 복사본을 만듭니다. 이 전략은 `CollectiveOps`라는 수집을 위한 통신용 텐서플로 연산을 사용하여 그래디언트를 모으고, 변수들의 값을 동일하게 맞춥니다. [텐서플로로 분산 훈련하기](../../guide/distributed_training.ipynb)에 이 전략에 대한 더 자세한 내용이 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "1uFSHCJXMrQ-"
      },
      "outputs": [],
      "source": [
        "strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "H47DDcOgfzm7"
      },
      "source": [
        "## 모델 훈련 및 평가하기\n",
        "\n",
        "다음으로, 추정기의 `RunConfig`에 분산 전략을 지정하십시오. 그리고 `tf.estimator.train_and_evaluate`로 훈련 및 평가를 합니다. 이 튜토리얼에서는 `train_distribute`로만 전략을 지정하였기 때문에 훈련 과정만 분산 처리합니다. `eval_distribute`를 지정하여 평가도 분산 처리할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "BcsuBYrpgnlS"
      },
      "outputs": [],
      "source": [
        "config = tf.estimator.RunConfig(train_distribute=strategy)\n",
        "\n",
        "classifier = tf.estimator.Estimator(\n",
        "    model_fn=model_fn, model_dir='/tmp/multiworker', config=config)\n",
        "tf.estimator.train_and_evaluate(\n",
        "    classifier,\n",
        "    train_spec=tf.estimator.TrainSpec(input_fn=input_fn),\n",
        "    eval_spec=tf.estimator.EvalSpec(input_fn=input_fn)\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "XVk4ftYx6JAO"
      },
      "source": [
        "## 훈련 성능 최적화하기\n",
        "\n",
        "이제 모델과 `tf.distribute.Strategy`로 만든 다중 워커를 사용할 수 있는 추정기가 있습니다. 다중 워커 훈련 성능을 최적화하려면 다음과 같은 방법을 사용해 보십시오.\n",
        "\n",
        "*   *배치 크기 늘리기:* 여기서 지정하는 배치 크기는 GPU당 크기입니다. 일반적으로, GPU 메모리 크기에 맞는 한 가장 크게 배치 크기를 잡는 것이 좋습니다.\n",
        "*   *변수 형변환:* 가능하면 변수를 `tf.float` 타입으로 바꾸세요.  공식 ResNet 모델의 [예제](https://github.com/tensorflow/models/blob/8367cf6dabe11adf7628541706b660821f397dce/official/resnet/resnet_model.py#L466)에서 어떻게 변환하는지 볼 수 있습니다.\n",
        "*   *집합 통신 구현을 사용하세요:* `MultiWorkerMirroredStrategy`는 여러 가지 [집합 통신 구현](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/distribute/cross_device_ops.py)을 제공합니다.  \n",
        "    * `RING`은 장비 간 통신을 위하여 gRPC를 써서 링 네트워크 기반의 집합 통신을 구현한 것입니다.\n",
        "    * `NCCL`은 [Nvidia의 NCCL](https://developer.nvidia.com/nccl)을 사용하여 수집 연산을 구현한 것입니다.  \n",
        "    * `AUTO`는 런타임이 알아서 고르도록 합니다.\n",
        "    \n",
        "    어떤 집합 구현이 가장 좋은지는 GPU의 숫자와 종류, 클러스터 장비 간 네트워크 연결 등에 따라 다를 수 있습니다. 런타임 자동 선택을 오버라이드하려면, `MultiWorkerMirroredStrategy` 생성자의 `communication` 인자에 적절한 값을 주면 됩니다. 예를 들어 `communication=tf.distribute.experimental.CollectiveCommunication.NCCL`과 같이 주면 됩니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "AW0Hb2xM6EGX"
      },
      "source": [
        "## 다른 코드 예제\n",
        "\n",
        "1.   [처음부터 끝까지 살펴보는 예제](https://github.com/tensorflow/ecosystem/tree/master/distribution_strategy)에서는 tensorflow/ecosystem의 쿠버네티스(Kubernetes) 템플릿을 이용하여 다중 워커를 사용하여 훈련합니다. 이 예제에서는 케라스 모델을 만든 후, `tf.keras.estimator.model_to_estimator` API를 이용하여 추정기 모델로 변환합니다.\n",
        "2.   다중 분산 전략으로 실행할 수 있는 공식 [ResNet50](https://github.com/tensorflow/models/blob/master/official/resnet/imagenet_main.py) 모델."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "Tce3stUlHN0L"
      ],
      "name": "multi_worker_with_estimator.ipynb",
      "private_outputs": true,
      "provenance": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
